/*
 * Copyright 2018-2023 datagear.tech
 *
 * This file is part of DataGear.
 *
 * DataGear is free software: you can redistribute it and/or modify it under the terms of
 * the GNU Lesser General Public License as published by the Free Software Foundation,
 * either version 3 of the License, or (at your option) any later version.
 *
 * DataGear is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with DataGear.
 * If not, see <https://www.gnu.org/licenses/>.
 */

package org.datagear.persistence.support;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.datagear.meta.Column;
import org.datagear.meta.Table;
import org.datagear.persistence.Dialect;
import org.datagear.persistence.PersistenceException;
import org.datagear.persistence.Row;
import org.datagear.persistence.RowMapper;
import org.datagear.persistence.RowMapperException;
import org.datagear.util.JdbcSupport;
import org.datagear.util.QueryResultSet;
import org.datagear.util.Sql;
import org.datagear.util.SqlParamValue;

/**
 * 持久操作支持类。
 * 
 * @author datagear@163.com
 *
 */
public class PersistenceSupport extends JdbcSupport
{
	/**
	 * 转换为引号名字。
	 * 
	 * @param dialect
	 * @param name
	 * @return
	 */
	public String quote(Dialect dialect, String name)
	{
		return dialect.quote(name);
	}

	/**
	 * 执行数目查询。
	 * 
	 * @param cn
	 * @param sql
	 * @return
	 * @throws PersistenceException
	 */
	public long executeCountQueryWrap(Connection cn, Sql sql) throws PersistenceException
	{
		try
		{
			return executeCountQuery(cn, sql);
		}
		catch (SQLException e)
		{
			throw new PersistenceException(e);
		}
	}

	/**
	 * 执行数目查询。
	 * 
	 * @param cn
	 * @param queryView
	 * @return
	 * @throws PersistenceException
	 */
	public long executeCountQueryForQueryView(Connection cn, Sql queryView) throws PersistenceException
	{
		Sql countQuery = Sql.valueOf().sql("SELECT COUNT(*) FROM (").sql(queryView).sql(") T");
		return executeCountQueryWrap(cn, countQuery);
	}

	/**
	 * 执行更新。
	 * 
	 * @param cn
	 * @param sql
	 * @return
	 * @throws PersistenceException
	 */
	public int executeUpdateWrap(Connection cn, Sql sql) throws PersistenceException
	{
		try
		{
			return executeUpdate(cn, sql);
		}
		catch (SQLException e)
		{
			throw new PersistenceException(e);
		}
	}

	/**
	 * 执行更新。
	 * 
	 * @param cn
	 * @param sql
	 * @param autoGeneratedColumns
	 * @return
	 * @throws PersistenceException
	 */
	@SuppressWarnings("unchecked")
	public AutoGeneratedResult executeUpdateWrap(Connection cn, Sql sql, List<Column> autoGeneratedColumns)
			throws PersistenceException
	{
		if (autoGeneratedColumns == null || autoGeneratedColumns.isEmpty())
		{
			int updateCount = executeUpdateWrap(cn, sql);
			return new AutoGeneratedResult(updateCount, Collections.EMPTY_LIST);
		}

		String[] autoGeneratedNames = new String[autoGeneratedColumns.size()];
		int[] autoGeneratedTypes = new int[autoGeneratedColumns.size()];

		for (int i = 0; i < autoGeneratedColumns.size(); i++)
		{
			Column column = autoGeneratedColumns.get(i);
			autoGeneratedNames[i] = column.getName();
			autoGeneratedTypes[i] = column.getType();
		}

		try
		{
			return executeUpdate(cn, sql, autoGeneratedNames, autoGeneratedTypes);
		}
		catch (SQLException e)
		{
			throw new PersistenceException(e);
		}
	}

	/**
	 * 执行列表结果查询。
	 * 
	 * @param cn
	 * @param table
	 * @param sql
	 * @param resultSetType
	 * @return
	 * @throws PersistenceException
	 */
	public List<Row> executeListQuery(Connection cn, Table table, Sql sql, int resultSetType)
			throws PersistenceException
	{
		return executeListQuery(cn, table, sql, resultSetType, 1, -1, null);
	}

	/**
	 * 执行列表结果查询。
	 * 
	 * @param cn
	 * @param table
	 * @param sql
	 * @param resultSetType
	 * @param mapper
	 *            允许为{@code null}
	 * @return
	 * @throws PersistenceException
	 */
	public List<Row> executeListQuery(Connection cn, Table table, Sql sql, int resultSetType, RowMapper mapper)
			throws PersistenceException
	{
		return executeListQuery(cn, table, sql, resultSetType, 1, -1, mapper);
	}

	/**
	 * 执行列表结果查询。
	 * 
	 * @param cn
	 * @param table
	 * @param sql
	 * @param resultSetType
	 * @param startRow
	 *            起始行号，以{@code 1}开头
	 * @param count
	 *            读取行数，如果{@code <0}，表示读取全部
	 * @param mapper
	 *            允许为{@code null}
	 * @return
	 */
	public List<Row> executeListQuery(Connection cn, Table table, Sql sql, int resultSetType, int startRow, int count,
			RowMapper mapper) throws PersistenceException
	{
		QueryResultSet qrs = null;

		try
		{
			qrs = executeQuery(cn, sql, resultSetType);
			ResultSet rs = qrs.getResultSet();

			return mapToRows(cn, table, rs, startRow, count, mapper);
		}
		catch (SQLException e)
		{
			throw new PersistenceException(e);
		}
		finally
		{
			QueryResultSet.close(qrs);
		}
	}

	/**
	 * 将结果集映射至{@linkplain Row}洌表。
	 * 
	 * @param cn
	 * @param table
	 * @param rs
	 * @param startRow
	 *            起始行，以{@code 1}开头
	 * @param count
	 *            映射行数，{@code -1}表示全部
	 * @param mapper
	 *            允许为{@code null}
	 * @return
	 * @throws RowMapperException
	 * @throws SQLException
	 */
	protected List<Row> mapToRows(Connection cn, Table table, ResultSet rs, int startRow, int count, RowMapper mapper)
			throws RowMapperException, SQLException
	{
		if (startRow < 1)
			startRow = 1;

		List<Row> resultList = new ArrayList<>();

		if (count >= 0 && startRow > 1)
			forwardBefore(rs, startRow);

		int endRow = (count >= 0 ? startRow + count : -1);

		int rowIndex = startRow;
		while (rs.next())
		{
			if (endRow >= 0 && rowIndex >= endRow)
				break;

			Row row = mapToRow(cn, table, rs, rowIndex, mapper);

			resultList.add(row);

			rowIndex++;
		}

		return resultList;
	}

	/**
	 * 将结果集行映射为{@linkplain Row}对象。
	 * 
	 * @param cn
	 * @param table
	 * @param rs
	 * @param rowIndex
	 *            行号，以{@code 1}开头
	 * @return
	 * @throws RowMapperException
	 */
	public Row mapToRow(Connection cn, Table table, ResultSet rs, int rowIndex) throws RowMapperException
	{
		return mapToRow(cn, table, rs, rowIndex, null);
	}

	/**
	 * 将结果集行映射为{@linkplain Row}对象。
	 * 
	 * @param cn
	 * @param table
	 * @param rs
	 * @param rowIndex
	 *            行号，以{@code 1}开头
	 * @param mapper
	 *            允许为{@code null}
	 * @return
	 * @throws RowMapperException
	 */
	public Row mapToRow(Connection cn, Table table, ResultSet rs, int rowIndex, RowMapper mapper)
			throws RowMapperException
	{
		if (mapper != null)
			return mapper.map(cn, table, rs, rowIndex);
		else
		{
			Row row = new Row();

			try
			{
				Column[] columns = table.getColumns();
				for (int i = 0; i < columns.length; i++)
				{
					Column column = columns[i];

					if (!supportsColumn(column))
						continue;

					Object value = getColumnValue(cn, rs, column);
					row.put(column.getName(), value);
				}
			}
			catch (SQLException e)
			{
				throw new RowMapperException(e);
			}

			return row;
		}
	}

	public Object getColumnValue(Connection cn, ResultSet rs, Column column) throws SQLException
	{
		return getColumnValue(cn, rs, column.getName(), column.getType());
	}

	public SqlParamValue createSqlParamValue(Column column, Object value)
	{
		return new SqlParamValue(value, column.getType());
	}

	/**
	 * 是否指定指定列的持久化操作。
	 * 
	 * @param column
	 * @return
	 */
	public boolean supportsColumn(Column column)
	{
		return supportsSqlType(column.getType());
	}

	/**
	 * 是否指定指定SQL类型的持久化操作。
	 * 
	 * @param sqlType
	 * @return
	 */
	public boolean supportsSqlType(int sqlType)
	{
		switch (sqlType)
		{
			case Types.TINYINT:
			case Types.SMALLINT:
			case Types.INTEGER:
			case Types.BIGINT:
			case Types.REAL:
			case Types.FLOAT:
			case Types.DOUBLE:
			case Types.DECIMAL:
			case Types.NUMERIC:
			case Types.BIT:
			case Types.BOOLEAN:
			case Types.CHAR:
			case Types.VARCHAR:
			case Types.LONGVARCHAR:
			case Types.BINARY:
			case Types.VARBINARY:
			case Types.LONGVARBINARY:
			case Types.DATE:
			case Types.TIME:
			case Types.TIME_WITH_TIMEZONE:
			case Types.TIMESTAMP:
			case Types.TIMESTAMP_WITH_TIMEZONE:
			case Types.CLOB:
			case Types.BLOB:
			case Types.NCHAR:
			case Types.NVARCHAR:
			case Types.LONGNVARCHAR:
			case Types.NCLOB:
			case Types.SQLXML:
				return true;

			default:
				return false;
		}
	}
}
